package org.example.model;

import org.example.exception.DuplicateIdException;
import org.example.exception.ElementNotFoundException;
import org.example.exception.HTMLDocumentException;
import org.example.service.tree.VisualTreeViewer;
import org.example.service.tree.impl.HtmlNameProvider;
import org.example.service.tree.impl.HtmlTreeContentProvider;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.parser.Parser;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;

public class HTMLDocument {
    private Document document;


    /**
     * 构造方法，初始化一个默认的 HTML 文档，确保 <body> 元素具有 id="body"。
     */
    public HTMLDocument() {
        this.document = Jsoup.parse(
                "<html>\n" +
                        "  <head>\n" +
                        "    <title></title>\n" +
                        "  </head>\n" +
                        "  <body>\n" +
                        "  </body>\n" +
                        "</html>",
                "",
                Parser.htmlParser()
        );
    }

    public HTMLDocument(Document document) {
        this.document = document;
    }

    /**
     * 从文件中加载 HTML 文档。
     *
     * @param filepath HTML 文件的路径
     * @throws IOException 如果文件读取失败
     */
    public void loadFromFile(String filepath) throws IOException {
        File input = new File(filepath);
        this.document = Jsoup.parse(input, "UTF-8");
    }

    /**
     * 将 HTML 文档保存到文件。
     *
     * @param filepath 保存的文件路径
     * @throws IOException 如果文件写入失败
     */
    public void saveToFile(String filepath) throws IOException {
        Files.write(Paths.get(filepath), this.document.outerHtml().getBytes());
    }

    /**
     * 在指定元素之前插入一个新元素。
     *
     * @param newTagName 新元素的标签名（例如 "p", "div"）
     * @param newId      新元素的 ID
     * @param targetId   目标元素的 ID，新元素将插入到该元素之前
     * @param textContent 新元素的文本内容（可选）
     * @throws IllegalArgumentException 如果目标元素未找到或新 ID 已存在
     * @return 插入的元素element
     *
     */
    public Element insertElementBefore(final String newTagName, final String newId, final String targetId, final String textContent) throws ElementNotFoundException, DuplicateIdException {

        Element target;

        // 如果 targetId 是特定标签，则通过标签名获取元素
        if ("html".equalsIgnoreCase(targetId) || "head".equalsIgnoreCase(targetId) ||
                "title".equalsIgnoreCase(targetId) || "body".equalsIgnoreCase(targetId)) {
            target = document.getElementsByTag(targetId).first();
        } else {
            // 否则根据 ID 查找
            target = document.getElementById(targetId);
        }

        if (target == null) {
            throw new ElementNotFoundException("目标元素 ID '" + targetId + "' 未找到。");
        }

        if("html".equalsIgnoreCase(newId) || "head".equalsIgnoreCase(newId) ||
                "title".equalsIgnoreCase(newId) || "body".equalsIgnoreCase(newId)){
            throw new DuplicateIdException("新元素 ID '" + newId + "' 不能为html,head,title,body。");
        }
        if (newId != null && document.getElementById(newId) != null) {
            throw new DuplicateIdException("新元素 ID '" + newId + "' 已存在。");
        }

        Element newElement = new Element(newTagName).attr("id", newId != null ? newId : newTagName);
        if (textContent != null && !textContent.isEmpty()) {
            newElement.text(textContent);
        }

        target.before(newElement);
        return newElement;
    }

    /**
     * 在指定元素内部追加一个子元素。
     *
     * @param newTagName 新元素的标签名（例如 "p", "div"）
     * @param newId      新元素的 ID
     * @param targetId   父元素的 ID，新元素将被追加到该元素内部
     * @param textContent 新元素的文本内容（可选）
     * @throws IllegalArgumentException 如果父元素未找到或新 ID 已存在
     * @return 被添加的element
     */
    public Element appendElement(final String newTagName, final String newId, final String targetId, final String textContent) throws ElementNotFoundException, DuplicateIdException {

        Element target;

        // 如果 targetId 是特定标签，则通过标签名获取元素
        if ("html".equalsIgnoreCase(targetId) || "head".equalsIgnoreCase(targetId) ||
                "title".equalsIgnoreCase(targetId) || "body".equalsIgnoreCase(targetId)) {
            target = document.getElementsByTag(targetId).first();
        } else {
            // 否则根据 ID 查找
            target = document.getElementById(targetId);
        }

        if (target == null) {
            throw new ElementNotFoundException("目标元素 ID '" + targetId + "' 未找到。");
        }

        if ("html".equalsIgnoreCase(newId) || "head".equalsIgnoreCase(newId) ||
                "title".equalsIgnoreCase(newId) || "body".equalsIgnoreCase(newId)) {
            throw new DuplicateIdException("新元素 ID '" + newId + "' 不能为 html, head, title, body。");
        }

        if (newId != null && document.getElementById(newId) != null) {
            throw new DuplicateIdException("新元素 ID '" + newId + "' 已存在。");
        }

        Element newElement = new Element(newTagName).attr("id", newId != null ? newId : newTagName);
        if (textContent != null && !textContent.isEmpty()) {
            newElement.text(textContent);
        }

        target.appendChild(newElement);  // 在目标元素内部添加子元素

        return newElement;
    }


    /**
     * 删除指定 ID 的元素。
     *
     * @param targetId 要删除的元素的 ID
     * @throws IllegalArgumentException 如果元素未找到
     * @return 包含已删除元素信息的 DeletedElementInfo 对象
     */
    public DeletedElementInfo deleteElement(final String targetId) throws HTMLDocumentException {

        Element target;

        // 如果 targetId 是特定标签，则通过标签名获取元素
        boolean isSpecialTags = "html".equalsIgnoreCase(targetId) || "head".equalsIgnoreCase(targetId) ||
                "title".equalsIgnoreCase(targetId) || "body".equalsIgnoreCase(targetId);
        if (isSpecialTags) {
            target = document.getElementsByTag(targetId).first();
        } else {
            // 否则根据 ID 查找
            target = document.getElementById(targetId);
        }

        if (target == null) {
            throw new ElementNotFoundException("目标元素 ID '" + targetId + "' 未找到。");
        }

        if (isSpecialTags) {
            throw new HTMLDocumentException("不能删除根元素 html, head, title, body。");
        }

        Element parentElement = target.parent();

        // 记录被删除元素的原始索引位置
        assert parentElement != null;
        int originalIndex = parentElement.children().indexOf(target);

        // 克隆目标元素以便之后还原
        Element removedElement = target.clone();

        target.remove();  // 从 DOM 中删除目标元素

        // 返回包含已删除元素、其父元素和原始索引位置的信息
        return new DeletedElementInfo(removedElement, parentElement, originalIndex);
    }


    /**
     * 修改指定元素的 ID。
     *
     * @param targetId 要修改的元素的当前 ID
     * @param newId    新的 ID
     * @throws IllegalArgumentException 如果元素未找到，或者新 ID 已存在，或新 ID 非法
     * @return 被修改的element
     */
    public Element editElementId(final String targetId, final String newId) throws DuplicateIdException, ElementNotFoundException {

        Element target;

        // 如果 targetId 是特定标签，则通过标签名获取元素
        if ("html".equalsIgnoreCase(targetId) || "head".equalsIgnoreCase(targetId) ||
                "title".equalsIgnoreCase(targetId) || "body".equalsIgnoreCase(targetId)) {
            target = document.getElementsByTag(targetId).first();
        } else {
            target = document.getElementById(targetId);
        }

        if (target == null) {
            throw new ElementNotFoundException("目标元素 ID '" + targetId + "' 未找到。");
        }

        if ("html".equalsIgnoreCase(newId) || "head".equalsIgnoreCase(newId) ||
                "title".equalsIgnoreCase(newId) || "body".equalsIgnoreCase(newId)) {
            throw new IllegalArgumentException("新 ID '" + newId + "' 不能为html, head, title, body。");
        }

        if (document.getElementById(newId) != null) {
            throw new DuplicateIdException("新元素 ID '" + newId + "' 已存在。");
        }

        target.attr("id", newId);  // 修改 ID 属性
        return target;
    }


    /**
     * 修改指定元素的文本内容。
     *
     * @param targetId    要修改的元素的 ID
     * @param newText     新的文本内容
     * @throws IllegalArgumentException 如果元素未找到
     * @return 被修改的element
     */
    public Element editElementText(final String targetId, final String newText) throws ElementNotFoundException {

        Element target;

        // 如果 targetId 是特定标签，则通过标签名获取元素
        if ("html".equalsIgnoreCase(targetId) || "head".equalsIgnoreCase(targetId) ||
                "title".equalsIgnoreCase(targetId) || "body".equalsIgnoreCase(targetId)) {
            target = document.getElementsByTag(targetId).first();
        } else {
            target = document.getElementById(targetId);
        }

        if (target == null) {
            throw new ElementNotFoundException("目标元素 ID '" + targetId + "' 未找到。");
        }

        // 获取当前的子元素，保留它们
        List<Element> children = target.children();

        // 清除文本节点，但保留子元素
        target.text(newText);

        // 重新添加子元素
        for (Element child : children) {
            target.appendChild(child);
        }
        return target;
    }

    public String getElementText(String targetId) throws ElementNotFoundException {
        Element target = document.getElementById(targetId);
        if(target == null){
            throw new ElementNotFoundException("目标元素 ID '" + targetId + "' 未找到。");
        }
        return  target.ownText();
    }



    /**
     * 获取底层的 Jsoup Document 对象。
     *
     * @return Jsoup Document 对象
     */
    public Document getDocument() {
        return document;
    }

    /**
     * 设置底层的 Jsoup Document 对象。
     *
     * @param document Jsoup Document 对象
     */
    public void setDocument(Document document) {
        this.document = document;
    }

    public HTMLDocument init(){
        this.document = Jsoup.parse(
                "<html>\n" +
                        "  <head>\n" +
                        "    <title></title>\n" +
                        "  </head>\n" +
                        "  <body>\n" +
                        "  </body>\n" +
                        "</html>",
                "",
                Parser.htmlParser()
        );
        return this;
    }

}
